import { musicBasicSettingStore } from '@/store/musicBasicSettingStore'
const playerStatus = ['play', 'prepare', 'notLoaded', 'onError'] as const
type PlayerStatus = (typeof playerStatus)[number]

let audioCtx: AudioContext | null = null
let analyser: AnalyserNode | null = null
let gain: GainNode | null = null
function initCtx() {
  if (!audioCtx) {
    audioCtx = new AudioContext()
    analyser = audioCtx.createAnalyser()
    gain = audioCtx.createGain()
    gain!.connect(audioCtx!.destination)
    gain.gain.value = musicBasicSettingStore.value.volume
  }
}
const bufferCache = new Map<string, AudioBuffer>()
const getBuffer = async (src: string): Promise<AudioBuffer> => {
  if (bufferCache.has(src)) {
    const origin = bufferCache.get(src)!
    return origin
  }

  const arrayBuffer = await fetch(src).then((res) => res.arrayBuffer())
  initCtx()
  const buffer: AudioBuffer = await audioCtx!.decodeAudioData(arrayBuffer)
  bufferCache.set(src, buffer)
  return buffer
}
class AudioPlayerClient {
  state: PlayerStatus = 'notLoaded'
  private buffer: AudioBuffer | null = null
  private bufferSource: AudioBufferSourceNode | null = null
  private _src: string
  private playEndCallback?: () => any
  private onLoadPromiseResolve?: () => void

  public get src() {
    return this._src
  }

  constructor(src: string) {
    this._src = src

    getBuffer(src)
      .then((buffer) => {
        this.buffer = buffer
        if (this.state == 'notLoaded') {
          this.state = 'prepare'
          this.onLoadPromiseResolve && this.onLoadPromiseResolve()
        }
      })
      .catch((e) => {
        this.state = 'onError'
        console.error(`AudioPlayerClient加载失败:${src}`, e)
      })
  }
  async play() {
    if (this.state != 'prepare') {
      console.error(`AudioPlayerClient无法播放,当前状态:${this.state}`)
      return
    }
    const bufferSource = audioCtx!.createBufferSource()
    bufferSource.buffer = this.buffer!
    bufferSource.connect(analyser!)
    bufferSource.connect(gain!)

    this.state = 'play'
    bufferSource.start()
    bufferSource.addEventListener('ended', () => {
      this.stop(false)
    })
    this.bufferSource = bufferSource
  }
  async stop(active = true) {
    if (this.state != 'play') {
      console.warn(`AudioPlayerClient无法暂停,当前状态:${this.state}`)
      return
    }
    if (active) this.bufferSource?.stop()
    this.state = 'prepare'
    this.playEndCallback && this.playEndCallback()
  }
  onLoad() {
    const p = new Promise<void>((resolve) => {
      this.onLoadPromiseResolve = resolve
    })
    if (this.state == 'prepare') {
      this.onLoadPromiseResolve!()
    }
    return p
  }
  async onPlayerEndOnce(playEndCallback: () => any) {
    this.playEndCallback = playEndCallback
  }
}

interface PlayParams {
  src: string
  volume?: number
}

class AudioPlayerPool {
  private pool: Map<string, { client: AudioPlayerClient; isUse: boolean }[]> =
    new Map()
  set volume(volume: number) {
    if (gain) gain.gain.value = volume
  }
  async play(params: PlayParams) {
    if (audioCtx == null) {
      initCtx()
    }
    if (params.volume) {
      gain!.gain.value = params.volume
    }
    const { src } = params
    const item = this.getPrepareClient(src)

    if (item != null) {
      item.isUse = true
      item.client.play()
      item.client.onPlayerEndOnce(() => {
        item.isUse = false
      })
      return
    }
    const client = new AudioPlayerClient(src)
    await client.onLoad()
    const newItem = { client, isUse: true }
    if (!this.pool.has(src)) {
      this.pool.set(src, [newItem])
    } else {
      this.pool.get(src)!.push(newItem)
    }
    client.play()
    newItem.client.onPlayerEndOnce(() => {
      newItem.isUse = false
    })
  }
  getByteFrequencyData() {
    if (analyser == null) return new Uint8Array(512).fill(0)
    const frequencyData = new Uint8Array(analyser.frequencyBinCount)
    analyser.getByteFrequencyData(frequencyData)
    const arr = new Uint8Array(512)
    for (let i = 0; i < arr.length; i++) {
      arr[i] = (frequencyData[i * 2] + frequencyData[i * 2 + 1]) / 2
    }
    return arr
  }
  async preloading(...srcs: string[]) {
    for (let src of srcs) {
      if (this.getPrepareClient(src) != null) {
        continue
      }
      const client = new AudioPlayerClient(src)
      await client.onLoad()
      const newItem = { client, isUse: false }
      if (!this.pool.has(src)) {
        this.pool.set(src, [newItem])
      } else {
        this.pool.get(src)!.push(newItem)
      }
    }
  }
  private getPrepareClient(src: string) {
    if (!this.pool.has(src)) return
    const arr = this.pool.get(src)!
    for (let item of arr) {
      if (item.isUse) continue
      return item
    }
    return null
  }
}

export default new AudioPlayerPool()
